package pl.edu.agh.abd.hsmest;

import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Queue;

import pl.cyfronet.gforge.hsmest.hsmmon.stubs.Drive;
import pl.cyfronet.gforge.hsmest.hsmmon.stubs.HSMDriveState;
import pl.cyfronet.gforge.hsmest.hsmmon.stubs.HSMFileInfo;
import pl.cyfronet.gforge.hsmest.hsmmon.stubs.HSMLibrary;
import pl.cyfronet.gforge.hsmest.hsmmon.stubs.HSMMonPortType;
import pl.cyfronet.gforge.hsmest.hsmmon.stubs.HSMState;
import pl.cyfronet.gforge.hsmest.hsmmon.stubs.LibrariesInfo;
import pl.cyfronet.gforge.hsmest.hsmmon.stubs.Tape;
import pl.cyfronet.gforge.hsmest.hsmmon.stubs.TapeArray;
import pl.cyfronet.gforge.hsmest.hsmmon.stubs.VoidType;
import pl.edu.agh.abd.hsmest.event.FileLoaded;
import pl.edu.agh.abd.hsmest.event.TapeInserted;
import pl.edu.agh.abd.hsmest.event.TapeRemoved;
import pl.edu.agh.abd.hsmest.event.TapeRollback;

public class Simulator {

	private TapeArray tapeMap;
	private HSMLibrary library;
	private Estimator estimator;
	
	public Simulator(HSMMonPortType hsmMon, Estimator estimator) {

		this.estimator = estimator;
		
		try {
			tapeMap = hsmMon.getHSMTapeMap(new VoidType());

			LibrariesInfo libsInfo = hsmMon.getHSMLibraryInfo(new VoidType());
			for(HSMLibrary lib : libsInfo.getLibraries()) {
				if(lib.getId() == estimator.getDesiredLibraryId()) {
					library = lib;
					break;
				}
			}
			
		} catch (RemoteException e) {
			e.printStackTrace();
		}
	}
	
	public void processState(HSMState state, Queue<Event> queue) {
		
		//check if there are tapes inside drives
		//that contain files in file queue,
		//then start transfering data to disk cache
		checkLoadedTapes(state, queue);
		
		//load first file from file queue to first empty drive
		tryLoadTape(state, queue);
		
		//remove (rollback and unmount) first idle tape from drive
		tryUnloadTape(state, queue);
	}
	
	private void checkLoadedTapes(HSMState state, Queue<Event> queue) {
		
		HSMDriveState[] drives = state.getDrive();
		for(HSMDriveState drive : drives) {
			
			if(drive.getUsage() != Estimator.USAGE_IDLE) {
				continue;
			}
			
			List<HSMFileInfo> filesForTape = new ArrayList<HSMFileInfo>();
			
			HSMFileInfo[] fileQueue = state.getFileQueue();
			for(HSMFileInfo fileInfo : fileQueue) {
				
				if(fileInfo.getTapeID().equals(drive.getTapeID())) {
					
					filesForTape.add(fileInfo);
					
					HSMFileInfo[] newQueue = removeFileFromQueue(fileInfo, fileQueue);
					state.setFileQueue(newQueue);
				}
			}
			
			if(filesForTape.size() > 0) {
				drive.setUsage(Estimator.USAGE_INUSE);

				long time = 0;
				for(int i=0; i < filesForTape.size(); i++) {
					HSMFileInfo fileForTape = filesForTape.get(i);
					
					time += estimator.getCurrentTime() + estimateFileLoadTime(fileForTape, drive);
					FileLoaded ev = new FileLoaded(estimator.getCurrentTime() + time);
					
					if( i==filesForTape.size() -1) {
						ev.setLastForTape(true);
					} else {
						ev.setLastForTape(false);
					}
					
					queue.add(ev);
				}
			}
		}
	}
	
	private void tryLoadTape(HSMState state, Queue<Event> queue) {
		
		HSMFileInfo[] fileQueue = state.getFileQueue();
		
		if(fileQueue.length > 0 && !estimator.isLibraryBusy()) {
			
			HSMDriveState emptyDevice = null;
			for(HSMDriveState drive : state.getDrive()) {
				if(drive.getUsage() == Estimator.USAGE_EMPTY) {
					emptyDevice = drive;
					break;
				}
			}
			if(emptyDevice == null) {
				return;
			}
			
			String tapeId = fileQueue[0].getTapeID();
			
			emptyDevice.setTapeID(tapeId);
			emptyDevice.setUsage(Estimator.USAGE_INUSE);
			estimator.setLibraryBusy(true);
			
			long mountTime = library.getMountTime();
			long loadTime = estimateTapeLoadTime(emptyDevice);
			TapeInserted ev = new TapeInserted(estimator.getCurrentTime() + mountTime + loadTime);
			ev.setDrive(emptyDevice);
			
			queue.add(ev);
		}
	}
	private void tryUnloadTape(HSMState state, Queue<Event> queue) {
		
		HSMFileInfo[] fileQueue = state.getFileQueue();
		
		if(fileQueue.length > 0) {
			
			HSMDriveState idleDevice = null;
			for(HSMDriveState drive : state.getDrive()) {
				if(drive.getUsage() == Estimator.USAGE_IDLE) {
					idleDevice = drive;
					break;
				}
			}
			if(idleDevice == null) {
				return;
			}
			
			if(idleDevice.getCurrentPosition() != 0) {
				
				idleDevice.setUsage(Estimator.USAGE_INUSE);
				long rollbackTime = estimateTapeRollbackTime(idleDevice);
				TapeRollback ev = new TapeRollback(estimator.getCurrentTime() + rollbackTime);
				ev.setDrive(idleDevice);
				
				queue.add(ev);
				
			} else if(!estimator.isLibraryBusy()) {
				
				idleDevice.setUsage(Estimator.USAGE_EMPTY);
				long unmountTime = library.getDismountTime(); 
				long unloadTime = estimateTapeUnloadTime(idleDevice);
				TapeRemoved ev = new TapeRemoved(estimator.getCurrentTime() + unmountTime + unloadTime);
				ev.setDrive(idleDevice);
				
				queue.add(ev);
			}
		}
	}
	
	private HSMFileInfo[] removeFileFromQueue(HSMFileInfo file, HSMFileInfo[] queue) {
		
		List<HSMFileInfo> list= Arrays.asList(queue);
		list.remove(file);
		return list.toArray(new HSMFileInfo[0]);
	}
	
	private long estimateFileLoadTime(HSMFileInfo file, HSMDriveState drive) {
		
		int blkSize = file.getEndBlock() - file.getStartBlock();
		String tapeId = file.getTapeID();
		int block = 0;
		
		for(Tape tape : tapeMap.getTapeMap()) {
		
			if(tape.getTapeID().equals(tapeId)) {
				
				block = tape.getBlockSize();
			}
		}
		long size = (long)blkSize*block;
		float readRate = 1;
		
		Drive[] drives = library.getDrive();
		for(Drive driveInfo : drives) {
			if(driveInfo.getId() == drive.getId()) {
				readRate = driveInfo.getReadTransferRate();
			}
		}
		
		return (long)(size*readRate);
	}
	
	private long estimateTapeLoadTime(HSMDriveState drive) {
		
		int driveId = drive.getId();
		Drive[] drives = library.getDrive();
		for(Drive driveInfo : drives) {
			if(driveInfo.getId() == driveId) {
			
				return (long)driveInfo.getLoadTime();
			}
		}
		return 0;
	}
	
	private long estimateTapeRollbackTime(HSMDriveState drive) {
		
		int driveId = drive.getId();
		Drive[] drives = library.getDrive();
		for(Drive driveInfo : drives) {
			if(driveInfo.getId() == driveId) {
			
				return (long)driveInfo.getAveragePosTime();
			}
		}
		return 0;
		
	}
	
	private long estimateTapeUnloadTime(HSMDriveState drive) {
		

		int driveId = drive.getId();
		Drive[] drives = library.getDrive();
		for(Drive driveInfo : drives) {
			if(driveInfo.getId() == driveId) {
			
				return (long)driveInfo.getUnloadTime();
			}
		}
		return 0;
	}
}
